package com.alibaba.csb.ws.sdk;

import com.alibaba.csb.sdk.CsbSDKConstants;
import com.alibaba.csb.sdk.security.SignUtil;
import com.alibaba.csb.ws.sdk.internal.AxisStubDynamicProxyHandler;
import com.alibaba.csb.ws.sdk.internal.BindingDynamicProxyHandler;
import org.apache.axis.client.Stub;

import javax.net.ssl.*;
import javax.xml.ws.BindingProvider;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Bind accessKey, secretKey into the WS client caller (i.e. Dispath, Proxy)
 * 
 * <p>
 * The tool adds the security headers (as http headers) internally when sending a soap
 * request, the headers include the following:
 * 
 * <pre>
 * {@code
 * _api_name:api_name
 * _api_version:api_version
 * _api_access_key:abc   //the access key 
 * _api_timestamp:1464603102715  //the current timestamp of invoker machine
 * _api_fingerprint:invoke   // the invocation web service method 
 * _api_signature:FLHVBMMjaxMPUHy+2VCGPvRNcao= //the signature string calculated with ak, timestamp, fingerprint and sk 
 *}
 * </pre>
 * 
 * <pre>
 * {@code
 * Usage:
 * 
 *   //obtain dispatch or proxy
 *   Dispatch patch = ...
 *   
 *   
 *   //bind the dispatch with ak/sk
 *   WSParams params = WSParams.create();
 *   params.accessKey("xxxxx");
 *   params.secretKey("xxxxx");
 *   params.api("xxxxx");
 *   params.version("xxxxx");
 *   dispatch = WSClientSDK.bind(dispatch, params);
 * 
 *   //invoke the methods with the returned dispatch
 *   ret = dispatch.invoke(...);
 * 
 * }
 * </pre>
 * 
 * @author Alibaba Middleware CSB Team 
 * @author liaotian.wq
 * @since  2016
 * 
 * 
 */
public class WSClientSDK {
	private static boolean warmupFlag = false;
	private static final String BOUND_HANDLER_KEY = "__DynamicProxyHandler";
	public static final boolean PRINT_SIGNINFO = Boolean.getBoolean("WSClientSDK.print.signinfo");
    private static AtomicReference<String> BIZ_ID_KEY = new AtomicReference<String>();

    static {
        disableSslVerification();
    }

    private static void disableSslVerification() {
        try {
            // Create a trust manager that does not validate certificate chains
            TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                }

                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                }
            }
            };

            // Install the all-trusting trust manager
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

            // Create all-trusting host name verifier
            HostnameVerifier allHostsValid = new HostnameVerifier() {
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            };

            // Install the all-trusting host verifier
            HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
    }

	/**
	 * 加载HttpSDK所需要的类 (如，签名相关的)
	 * 
	 * 注意：这是一个高时间代价的启动方法，建议在整个JVM范围内，在使用HttpCaller调用具体的服务前，调用且只调用一次
	 */
	public static synchronized void warmup() {
		if (warmupFlag) {
			return; 
		}
		SignUtil.warmup();
		warmupFlag = true;
	}

    /**
     * init bizIdKey
     *
     * @param bizIdKey
     */
    public static void bizIdKey(String bizIdKey) {
        BIZ_ID_KEY.compareAndSet(null, bizIdKey);
    }

    /**
     * get bizIdkey
     *
     * @return
     */
    public static String bizIdKey() {
        String bizIdKey = BIZ_ID_KEY.get();
        return bizIdKey == null ? CsbSDKConstants.BIZID_KEY : bizIdKey;
    }

	/**
	 * 给proxy/dispath 绑定ak/sk安全对
	 * 
	 * @param proxy        客户端proxy或者dispatch
	 * @param accessKey    accessKey
	 * @param secretKey    secretKey
	 * @return             封装了accessKey和secretKey的proxy或者dispath, 调用逻辑要使用这个返回进行WS方法调用
	 * @throws WSClientException
	 *
	 * @deprecated 使用 bind(T proxy, WSParams params)
	 */
	public static <T> T bind(T proxy, String accessKey, String secretKey) throws WSClientException {
		return bind(proxy, accessKey, secretKey, null, null);
	}

	/**
	 * 把签名和调用相关的参数绑定到ws客户端，以便调用时在SOAP请求中生成签名验证相关的http headers
	 *
	 * @param proxy
	 * @param params
	 * @param <T>
	 * @return
	 * @throws WSClientException
	 */
	public static <T> T bind(T proxy, WSParams params) throws WSClientException {
		validateProxy(proxy);

		BindingDynamicProxyHandler handler = getHandler((BindingProvider)proxy);
		handler.setParams(params);
		return handler.bind(proxy);
	}

	public static <T extends Stub> Object bind(T proxy, WSParams params) throws WSClientException {
		AxisStubDynamicProxyHandler handler = new AxisStubDynamicProxyHandler();
		handler.setParams(params);
		return handler.bind(proxy);
	}

	/**
	 *
	 * @param proxy
	 * @param accessKey
	 * @param secretKey
	 * @param apiName
	 * @param apiVersion
	 * @param printHeaders
	 * @param <T>
	 * @return
	 * @throws WSClientException
	 *
	 * @deprecated 使用 bind(T proxy, WSParams params)
	 */
	public static <T> T bind(T proxy, String accessKey, String secretKey, String apiName, String apiVersion, boolean printHeaders) throws WSClientException {
		validateProxy(proxy);
		
		BindingDynamicProxyHandler handler = getHandler((BindingProvider)proxy);
		WSParams params = WSParams.create().accessKey(accessKey).secretKey(secretKey).api(apiName).version(apiVersion).debug(printHeaders);
		handler.setParams(params);
		return handler.bind(proxy);
	}
	
	/**
	 * 给proxy/dispath 绑定ak/sk安全对，及要调用的apiName和apiVersion
	 * 
	 * @param proxy        客户端proxy或者dispatch
	 * @param accessKey    accessKey
	 * @param secretKey    secretKey
	 * @param apiName      服务名
	 * @param apiVersion   服务版本
	 * @return             封装了accessKey和secretKey的proxy或者dispath, 调用逻辑要使用这个返回进行WS方法调用
	 * @throws WSClientException
	 *
	 * @deprecated 使用 bind(T proxy, WSParams params)
	 */
	public static <T> T bind(T proxy, String accessKey, String secretKey, String apiName, String apiVersion) throws WSClientException {
		return bind(proxy, accessKey, secretKey, apiName, apiVersion, false);
	}
	
	/**
	 * 设置直接从服务端返回预定义的MockResponse 
	 * @param proxy    客户端proxy或者dispatch
	 * @param isMock   是否使用mock
	 * @return         封装了mock标志的proxy或者dispath
	 * @throws WSClientException
	 *
	 * @deprecated 使用 bind(T proxy, WSParams params) 使用WSParams来设置是否为mockResponse
	 */
	public static <T> T setResponseMock(T proxy, boolean isMock) throws WSClientException {
		validateProxy(proxy);

		BindingDynamicProxyHandler handler = getHandler((BindingProvider)proxy);
		handler.setMock(isMock);
		return handler.bind(proxy);
	}
	
	private static BindingDynamicProxyHandler getHandler(BindingProvider bp) 
	{
		BindingDynamicProxyHandler handler = (BindingDynamicProxyHandler)bp.getRequestContext().get(BOUND_HANDLER_KEY);
		if (handler != null) {
			return handler;
		}
		handler = new BindingDynamicProxyHandler();
		//hold the reference of the handler, make they have the same lifecycle
		bp.getRequestContext().put(BOUND_HANDLER_KEY, handler); 
		
		return handler;
	}
	
	private static void validateProxy(Object proxy) throws WSClientException{
		if (proxy == null) {
			throw new WSClientException("proxy parameter is null");
		}

		if (!(proxy instanceof BindingProvider)) {
			throw new WSClientException("proxy is not a legal soap client");
		}
	}

	public static Map<String, String> genExtHeader(String fingerStr) {
		Map<String, String> extSignHeaderMap = new HashMap<String, String>();
		if (fingerStr != null) {
			extSignHeaderMap.put(CsbSDKConstants.HEADER_FINGERPRINT, fingerStr);
		}

		return extSignHeaderMap;
	}

	/**
	 * 生成签名相关的HTTP所有请求头
	 *
	 * @param params
	 * @return
	 */
	public static Map<String, String> generateSignHeaders(WSParams params) {
		Map<String, String> extSignHeaderMap = genExtHeader(params.getFingerPrinter());
		Map<String, String> requestHeaders = SignUtil.newParamsMap(null, params.getApi(), params.getVersion(),
				params.getAk(), params.getSk(), params.isTimestamp(), params.isNonce(), extSignHeaderMap, null, params.getSignImpl(),params.getVerifySignImpl());

		if (params.isMockRequest()) {
			requestHeaders.put(CsbSDKConstants.HEADER_MOCK, "true");
		}

		return requestHeaders;
	}
}
